<div class="tmd-doc">
<h1 class="tmd-header-1">
Some Panic/Recover Use Cases
</h1>
<p></p>
<div class="tmd-usual">
Panic and recover have been <a href="control-flows-more.html#panic-recover">introduced before</a>. The following of the current article will introduce some (good and bad) panic/recover use cases.
</div>
<p></p>
<p></p>
<h3 id="avoid-crashing" class="tmd-header-3">
Use Case 1: Avoid Panics Crashing Programs
</h3>
<p></p>
<div class="tmd-usual">
This should be the most popular use case of panic/recover. The use case is used commonly in concurrent programs, especially client-server programs.
</div>
<p></p>
<div class="tmd-usual">
An example:
</div>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

import "errors"
import "log"
import "net"

func main() {
	listener, err := net.Listen("tcp", ":12345")
	if err != nil {
		log.Fatalln(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
		}
		// Handle each client connection
		// in a new goroutine.
		go ClientHandler(conn)
	}
}

func ClientHandler(c net.Conn) {
	defer func() {
		if v := recover(); v != nil {
			log.Println("capture a panic:", v)
			log.Println("avoid crashing the program")
		}
		c.Close()
	}()
	panic(errors.New("just a demo.")) // a demo-purpose panic
}
</code></pre>
<p></p>
<div class="tmd-usual">
Start the server and run <code class="tmd-code-span">telnet localhost 12345</code> in another terminal, we can observe that the server will not crash down for the panics created in each client handler goroutine.
</div>
<p></p>
<div class="tmd-usual">
If we don't recover the potential panic in each client handler goroutine, the potential panic will crash the program.
</div>
<p></p>
<h3 id="auto-restart" class="tmd-header-3">
Use Case 2: Automatically Restart a Crashed Goroutine
</h3>
<p></p>
<div class="tmd-usual">
When a panic is detected in a goroutine, we can create a new goroutine for it. An example:
</div>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

import "log"
import "time"

func shouldNotExit() {
	for {
		// Simulate a workload.
		time.Sleep(time.Second)

		// Simulate an unexpected panic.
		if time.Now().UnixNano() &amp; 0x3 == 0 {
			panic("unexpected situation")
		}
	}
}

func NeverExit(name string, f func()) {
	defer func() {
		if v := recover(); v != nil {
			// A panic is detected.
			log.Println(name, "is crashed. Restart it now.")
			go NeverExit(name, f) // restart
		}
	}()
	f()
}

func main() {
	log.SetFlags(0)
	go NeverExit("job#A", shouldNotExit)
	go NeverExit("job#B", shouldNotExit)
	select{} // block here for ever
}
</code></pre>
<p></p>
<h3 id="long-jump" class="tmd-header-3">
Use Case 3: Use <code class="tmd-code-span">panic</code>/<code class="tmd-code-span">recover</code> Calls to Simulate Long Jump Statements
</h3>
<p></p>
<div class="tmd-usual">
Sometimes, we can use panic/recover as a way to simulate crossing-function long jump statements and crossing-function returns, though generally this way is not recommended to use. This way does harm for both code readability and execution efficiency. The only benefit is sometimes it can make code look less verbose.
</div>
<p></p>
<div class="tmd-usual">
In the following example, once a panic is created in an inner function, the execution will jump to the deferred call.
</div>
<pre class="tmd-code line-numbers">
<code class="language-go">package main

import "fmt"

func main() {
	n := func () (result int)  {
		defer func() {
			if v := recover(); v != nil {
				if n, ok := v.(int); ok {
					result = n
				}
			}
		}()

		func () {
			func () {
				func () {
					// ...
					panic(123) // panic on succeeded
				}()
				// ...
			}()
		}()
		// ...
		return 0
	}()
	fmt.Println(n) // 123
}
</code></pre>
<p></p>
<h3 id="avoid-verbose" class="tmd-header-3">
Use Case 4: Use <code class="tmd-code-span">panic</code>/<code class="tmd-code-span">recover</code> Calls to Reduce Error Checks
</h3>
<p></p>
<div class="tmd-usual">
An example:
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">func doSomething() (err error) {
	defer func() {
		switch e := recover().(type) {
		case nil:
		case error:
			err = e
		default:
			panic(e) // re-throw the panic
		}
	}()

	doStep1()
	doStep2()
	doStep3()
	doStep4()
	doStep5()

	return
}

// In reality, the prototypes of the doStepN functions
// might be different. Here, for each of them,
// * panic with nil for success and no needs to continue.
// * panic with error for failure and no needs to continue.
// * not panic for continuing.
func doStepN() {
	...
	if err != nil {
		panic(err)
	}
	...
	if done {
		panic(nil)
	}
}
</code></pre>
<p></p>
<div class="tmd-usual">
The above code is less verbose than the following one.
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">func doSomething() (err error) {
	shouldContinue, err := doStep1()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep2()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep3()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep4()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep5()
	if !shouldContinue {
		return err
	}

	return
}

// If err is not nil, then shouldContinue must be true.
// If shouldContinue is true, err might be nil or non-nil.
func doStepN() (shouldContinue bool, err error) {
	...
	if err != nil {
		return false, err
	}
	...
	if done {
		return false, nil
	}
	return true, nil
}
</code></pre>
<p></p>
<div class="tmd-usual">
However, usually, this panic/recover use pattern is not recommended to use. It is less Go-idiomatic and less efficient.
</div>
<p></p>
<div class="tmd-usual">
And please note that, since Go 1.21, a <code class="tmd-code-span">panic(nil)</code> call will become <a href="https://github.com/golang/go/issues/25448">eqivalent to <code class="tmd-code-span">panic(new(runtime.PanicNilError))</code></a>. So since Go 1.21, the above deferred function call should be written as
</div>
<p></p>
<pre class="tmd-code line-numbers">
<code class="language-go">func doSomething() (err error) {
	defer func() {
		switch e := recover().(type) {
		case nil, *runtime.PanicNilError:
		case error:
			err = e
		default:
			panic(e) // re-throw the panic
		}
	}()

	doStep1()
	...
}
</code></pre>
<p></p>
<p></p>
</div>
